﻿using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace SimpleORM
{
    public static class IQueryableExtension
    {
        #region Methods

        #region Methods - Save

        public static object Save(this IQueryable source)
        {
            IQueryContext qc = source as IQueryContext;
            if (qc == null)
                return null;

            if (qc.Settings.SaveInTransaction)
                return qc.ExecuteInTransaction();
            else
                return qc.Execute();
        }

        public static T Save<T>(this IQueryable<T> source)
        {
            object data = Save(source as IQueryable);
            if (data == null)
                return default(T);

            return (T)data;
        }

        #endregion

        #region Methods - Script

        public static IQueryable Combine(this IQueryable source, IQueryable target)
        {
            IQueryContext qc = source as IQueryContext;
            if (qc == null)
                return null;

            IQueryContext qcTarget = target as IQueryContext;
            if (qcTarget == null)
                return null;

            return qc.Combine(qcTarget);
        }

        public static IQueryable<T> Combine<T>(this IQueryable<T> source, IQueryable<T> target)
        {
            return Combine(source as IQueryable, target as IQueryable) as IQueryable<T>;
        }

        public static void Append(this IQueryable source, IQueryable target)
        {
            IQueryContext qc = source as IQueryContext;
            if (qc == null)
                return;

            IQueryContext qcTarget = target as IQueryContext;
            if (qcTarget == null)
                return;

            qc.Append(qcTarget);
        }

        public static void Append<T>(this IQueryable<T> source, IQueryable<T> target)
        {
            Append(source as IQueryable, target as IQueryable);
        }

        #endregion

        #region Methods - Add

        public static IQueryable<int> Add(this IQueryable source, object value)
        {
            Expression exp = Expression.Call(null,
                                             typeof(IQueryableExtension).GetMethod("Add",
                                             new Type[] { typeof(IQueryable), typeof(object) }),
                                             source.Expression,
                                             Expression.Constant(value));

            IQueryable<int> qc = source.Provider.CreateQuery<int>(exp);
            if (!(qc is IQueryContext))
                return qc;

            (qc as IQueryContext).Compile();

            return qc;
        }

        public static IQueryable<int> Add(this IQueryable source, KeyValueList<string, object> values)
        {
            return Add(source as IQueryable, DynamicClassBuilder.CreateInstance(values));
        }

        public static IQueryable<int> Add<T>(this IQueryable<T> source, T value)
        {
            return Add(source as IQueryable, value);
        }

        #endregion

        #region Methods - Update

        public static IQueryable<int> Update(this IQueryable source, object value)
        {
            MethodInfo method = typeof(IQueryableExtension).GetMethod("Update",
                                                   new Type[] { typeof(IQueryable), typeof(object) });
            Expression exp = Expression.Call(null,
                                             method,
                                             source.Expression,
                                             Expression.Constant(value));

            IQueryable<int> qc = source.Provider.CreateQuery<int>(exp);
            if (!(qc is IQueryContext))
                return qc;

            (qc as IQueryContext).Compile();

            return qc;
        }

        public static IQueryable<int> Update(this IQueryable source, KeyValueList<string, object> values)
        {
            return Update(source as IQueryable, DynamicClassBuilder.CreateInstance(values));
        }

        public static IQueryable<int> Update<T>(this IQueryable<T> source, T value)
        {
            return Update(source as IQueryable, value);
        }

        public static IQueryable<int> Update(this IQueryable source, string valueSelector, params object[] values)
        {
            if (source == null) throw new ArgumentNullException("source");
            if (valueSelector == null) throw new ArgumentNullException("valueSelector");
            LambdaExpression lambda = DynamicExpression.ParseLambda(source.ElementType, null, valueSelector, values);

            Expression exp = Expression.Call(
                    typeof(IQueryableExtension), "Update",
                    new Type[] { source.ElementType, lambda.Body.Type },
                    source.Expression, lambda);

            IQueryable<int> qc = source.Provider.CreateQuery<int>(exp);
            if (!(qc is IQueryContext))
                return qc;

            (qc as IQueryContext).Compile();

            return qc;
        }

        public static IQueryable<int> Update(this IQueryable source, Expression value)
        {
            Expression exp = Expression.Call(null,
                                             typeof(IQueryableExtension).GetMethod("Update",
                                             new Type[] { typeof(IQueryable), typeof(Expression) }),
                                             source.Expression,
                                             Expression.Quote(value));

            IQueryable<int> qc = source.Provider.CreateQuery<int>(exp);
            if (!(qc is IQueryContext))
                return qc;

            (qc as IQueryContext).Compile();

            return qc;
        }

        public static IQueryable<int> Update<T, TResult>(this IQueryable<T> source, Expression<Func<T, TResult>> value)
        {
            return Update(source as IQueryable, value);
        }

        #endregion

        #region Methods - Delete

        public static IQueryable<int> Delete(this IQueryable source)
        {
            Expression exp = Expression.Call(null,
                                             typeof(IQueryableExtension).GetMethod("Delete",
                                             new Type[] { typeof(IQueryable) }),
                                             source.Expression);

            IQueryable<int> qc = source.Provider.CreateQuery<int>(exp);
            if (!(qc is IQueryContext))
                return qc;

            (qc as IQueryContext).Compile();

            return qc;
        }

        public static IQueryable<int> Delete<T>(this IQueryable<T> source)
        {
            return Delete(source as IQueryable);
        }

        public static IQueryable<int> Delete(this IQueryable source, Type type)
        {
            Expression exp = Expression.Call(null,
                                             typeof(IQueryableExtension).GetMethod("Delete",
                                             new Type[] { typeof(IQueryable), typeof(Type) }),
                                             source.Expression, Expression.Constant(type));

            IQueryable<int> qc = source.Provider.CreateQuery<int>(exp);
            if (!(qc is IQueryContext))
                return qc;

            (qc as IQueryContext).Compile();

            return qc;
        }

        public static IQueryable<int> Delete<T>(this IQueryable<T> source, Type type)
        {
            return Delete(source as IQueryable, type);
        }

        #endregion

        #endregion
    }
}
